package com.sikaryofficial.backend.service.store;

import cn.hutool.core.collection.CollUtil;
import com.beust.jcommander.internal.Sets;
import com.google.common.collect.Lists;
import com.sikaryofficial.backend.constant.StoreTypeEnum;
import com.sikaryofficial.backend.domain.dto.resp.StoreListDTO;
import com.sikaryofficial.backend.domain.entity.Store;
import com.sikaryofficial.backend.service.IStoreService;
import com.sikaryofficial.backend.utils.PositionUtil;
import com.sikaryofficial.common.redis.service.RedisService;
import com.sikaryofficial.common.security.utils.SecurityUtils;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.Point;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import static com.sikaryofficial.backend.constant.RedisCacheKey.*;
import static com.sikaryofficial.backend.constant.StoreSourceEnum.MANUAL;

/**
 * @author : qinjinyuan
 * @desc : 店铺缓存对象
 * @date : 2023/11/06 11:02
 */
@Service
@Slf4j
public class StoreCacheService {

    /**
     * 缓存服务
     */
    @Autowired
    private IStoreService storeService;
    @Autowired
    private RedisService redisService;


    /**
     * 获取当前经纬度
     *
     * @param currentLongitude 当前经度
     * @param currentLatitude  当前纬度
     * @return
     */
    public List<StoreListDTO> findNearbyShopsCache(List<StoreListDTO> storeListDTOList, double currentLongitude, double currentLatitude) {
        if (CollUtil.isNotEmpty(storeListDTOList)) {
            log.info("nearModify-findNearbyShopsCache.size:{}", storeListDTOList.size());
            // 计算每个店铺与当前经纬度之间的距离
            storeListDTOList = findNearbyShops(storeListDTOList, currentLongitude, currentLatitude);
            storeListDTOList = storeListDTOList.stream().sorted((o1, o2) -> {
                if (Objects.isNull(o1.getDistance()) || Objects.isNull(o2.getDistance())) {
                    return -1;
                }
                return o1.getDistance().compareTo(o2.getDistance());
            }).collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
        }
        return storeListDTOList;
    }

    /**
     * 计算附近店铺的距离
     *
     * @param allShops
     * @param currentLongitude
     * @param currentLatitude
     * @return
     */
    public List<StoreListDTO> findNearbyShops(List<StoreListDTO> allShops, double currentLongitude, double currentLatitude) {
        List<StoreListDTO> nearbyShops = new ArrayList<>();
        for (StoreListDTO shop : allShops) {
            if (Objects.isNull(shop.getLongitude()) || Objects.isNull(shop.getLatitude())) {
                continue;
            }
            double calculateDistance = PositionUtil.getDistance(currentLongitude, currentLatitude, shop.getLongitude().doubleValue(), shop.getLatitude().doubleValue());
            // 假设距离小于等于1公里为附近店铺
            shop.setDistance(calculateDistance);
            nearbyShops.add(shop);
        }
        return nearbyShops;
    }

    /**
     * 计算附近店铺的距离
     *
     * @param shop
     * @param currentLongitude
     * @param currentLatitude
     * @return
     */
    public void calculateDistance(StoreListDTO shop, double currentLongitude, double currentLatitude) {
        if (Objects.isNull(shop.getLongitude()) || Objects.isNull(shop.getLatitude())) {
            log.info("calculateDistance getLongitude or getLatitude is empty.");
            return;
        }
        double calculateDistance = PositionUtil.getDistance(currentLongitude, currentLatitude, shop.getLongitude(), shop.getLatitude());
        log.info("calculateDistance:{}", calculateDistance);
        // 假设距离小于等于1公里为附近店铺
        shop.setDistance(calculateDistance);
    }

    /**
     * 计算附近店铺的距离
     *
     * @param shop
     * @param currentLongitude
     * @param currentLatitude
     * @return
     */
    public BigDecimal calculateDtoDistance(StoreListDTO shop, double currentLongitude, double currentLatitude) {
        if (Objects.isNull(shop.getLongitude()) || Objects.isNull(shop.getLatitude())) {
            log.info("calculateDistance getLongitude or getLatitude is empty.");
            return BigDecimal.ZERO;
        }
        double calculateDistance = PositionUtil.getDistance(currentLongitude, currentLatitude, shop.getLongitude(), shop.getLatitude());
        log.info("calculateDistance:{}", calculateDistance);
        // 假设距离小于等于1公里为附近店铺
        return BigDecimal.valueOf(calculateDistance).setScale(2, RoundingMode.HALF_UP);
    }

    /**
     * 计算附近店铺的距离
     *
     * @param shop
     * @param currentLongitude
     * @param currentLatitude
     * @return
     */
    public BigDecimal calculateEntityDistance(Store shop, double currentLongitude, double currentLatitude) {
        if (Objects.isNull(shop.getLongitude()) || Objects.isNull(shop.getLatitude())) {
            log.info("calculateDistance getLongitude or getLatitude is empty.");
            return BigDecimal.ZERO;
        }
        double calculateDistance = PositionUtil.getDistance(currentLongitude, currentLatitude, shop.getLongitude().doubleValue(), shop.getLatitude().doubleValue());
        log.info("calculateDistance:{}", calculateDistance);
        // 假设距离小于等于1公里为附近店铺
        return BigDecimal.valueOf(calculateDistance).setScale(2, RoundingMode.HALF_UP);
    }

    /**
     * 获取所有店铺缓存列表
     *
     * @return 所有店铺列表
     */
    public List<StoreListDTO> getAllStoreList() {
        long startTime = System.currentTimeMillis();
        Map<String, StoreListDTO> allMapData = redisService.getAllCacheMapByKey(STORE_ALL_LIST_CACHE);
        List<StoreListDTO> storeListDTOS = Lists.newArrayList();
        if (Objects.isNull(allMapData) || CollUtil.isEmpty(allMapData.values())) {
            storeListDTOS = storeService.getAllStoreList();
            log.info("StoreCacheService:getAllStoreList.size:{}", storeListDTOS.size());
            storeListDTOS.forEach(item -> redisService.setCacheMapValue(STORE_ALL_LIST_CACHE, item.getStoreId().toString(), item));
        } else {
            storeListDTOS.addAll(allMapData.values());
        }
        log.info("StoreCacheService:getAllStoreList cost {}ms", System.currentTimeMillis() - startTime);
        return storeListDTOS;
    }

    /**
     * 启动加载店铺点位信息
     */
    public void loadAllStorePoint() {
        // 清除店铺数据
        redisService.deleteObject(STORE_ALL_LIST_CACHE);
        List<StoreListDTO> allStoreList = getAllStoreList();
        if (CollUtil.isEmpty(allStoreList)) {
            return;
        }
        // 清除之前缓存
        redisService.deleteObject(STORE_POINT_LIST_CACHE);
        AtomicInteger count = new AtomicInteger();
        allStoreList.stream().filter(item -> Objects.nonNull(item.getLongitude()) && Objects.nonNull(item.getLatitude())).forEach(item -> {
            count.getAndIncrement();
            redisService.setCacheGeo(STORE_POINT_LIST_CACHE, item.getStoreId().toString(), new Point(item.getLongitude(), item.getLatitude()));
        });
        log.info("StoreCacheService:loadAllStorePoint.count:{}", count.get());
    }

    public void updateStoreCache(StoreListDTO item) {
        if (Objects.isNull(item)) {
            return;
        }
        redisService.setCacheMapValue(STORE_ALL_LIST_CACHE, item.getStoreId().toString(), item);
    }

    /**
     * 附近的商店列表
     *
     * @param longitude
     * @param latitude
     * @param distance
     * @return
     */
    public List<StoreListDTO> getNearReal(double longitude, double latitude, double distance) {
        List<String> members = redisService.getCacheRadius(STORE_POINT_LIST_CACHE, longitude, latitude, distance);
        if (CollUtil.isEmpty(members)) {
            return Lists.newArrayList();
        }
        log.info("nearModify-members.size:{}", members.size());
        Set<Long> storeIds = Sets.newHashSet();
        for (String member : members) {
            storeIds.add(Long.parseLong(member));
        }
        // 增加一个remove 方法
        String cacheKey = buildTop3ShopKey(longitude, latitude);
        List<StoreListDTO> cacheObj = redisService.getCacheObject(cacheKey);
        removeTop3(storeIds, cacheObj);

        List<StoreListDTO> allStoreList = getAllStoreList();
        List<StoreListDTO> filterStoreList = allStoreList.stream().filter(item ->
                MANUAL.getCode().equals(item.getSourceCode()) &&
                        item.getStoreId() != null && storeIds.contains(item.getStoreId())).collect(Collectors.toList());

        return findNearbyShopsCache(filterStoreList, longitude, latitude);
    }

    @NotNull
    private static String buildTop3ShopKey(double longitude, double latitude) {
        return MessageFormat.format(STORE_TOP3_CACHE, SecurityUtils.getUserId().toString(), longitude, latitude);
    }

    /**
     * 获取距离最近的top3的专卖店的店铺 (推荐)
     *
     * @param longitude
     * @param latitude
     * @param distance
     * @return
     */
    public List<StoreListDTO> top3(double longitude, double latitude, double distance) {
        String cacheKey = buildTop3ShopKey(longitude, latitude);
        List<StoreListDTO> cacheObj = redisService.getCacheObject(cacheKey);
        if (CollUtil.isNotEmpty(cacheObj)) {
            return cacheObj;
        }
        List<String> members = redisService.getCacheRadius(STORE_POINT_LIST_CACHE, longitude, latitude, distance);
        if (CollUtil.isEmpty(members)) {
            return Lists.newArrayList();
        }
        log.info("nearModify-members.size:{}", members.size());
        Set<Long> storeIds = Sets.newHashSet();
        for (String member : members) {
            storeIds.add(Long.parseLong(member));
        }
        List<StoreListDTO> allStoreList = getAllStoreList();
        // 获取所有专卖店
        List<StoreListDTO> filterStoreList = allStoreList.stream().filter(item ->
                MANUAL.getCode().equals(item.getSourceCode()) &&
                        item.getStoreId() != null && storeIds.contains(item.getStoreId()) &&
                        StoreTypeEnum.SPECIALTY.getCode().equals(item.getShopType())
        ).collect(Collectors.toList());
        List<StoreListDTO> allSpecialShops = findNearbyShopsCache(filterStoreList, longitude, latitude);
        if (CollUtil.isEmpty(allSpecialShops)) {
            return Lists.newArrayList();
        }
        List<StoreListDTO> top3 = allSpecialShops.subList(0, Math.min(allSpecialShops.size(), 3));
        redisService.setCacheObject(cacheKey, top3, 2L, TimeUnit.DAYS);
        return top3;
    }

    private void removeTop3(Set<Long> storeIds, List<StoreListDTO> cacheObj) {
        if (CollUtil.isEmpty(cacheObj)) {
            return;
        }
        // 移除top3对应的店铺ID
        cacheObj.stream().filter(item -> storeIds.contains(item.getStoreId())).forEach(item -> storeIds.remove(item.getStoreId()));
    }

}
